package pl.edu.agh.jemo.evolution.objfunc.impl.clustering;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;

import pl.edu.agh.jemo.evolution.objfunc.impl.PopulationCentroidObjectiveFunction;
import pl.edu.agh.jemo.evolution.population.IPopulation;
import pl.edu.agh.jemo.evolution.population.impl.Population;
import pl.edu.agh.jemo.evolution.specimen.ISpecimen;

public class Window {

	private List<List<Double>> boundaries;

	private List<LinkedList<ISpecimen>> sortedPopulations;

	private List<IntPair> specimensBounds; //indeksy skrajnych osobnikow na wymiarach
	private Set<ISpecimen> specimensInside;
	public int id;

	public Window(List<Double> initSize, List<List<Double>> populationBoundaries) {

		setSpecimensBounds(new ArrayList<IntPair>());
		setSpecimensInside(new HashSet<ISpecimen>());
		Random random = new Random();
		Double startPos;
		boundaries = new ArrayList<List<Double>>();
		for (int i = 0; i < initSize.size(); i++) {
			startPos = random.nextDouble()
					* (populationBoundaries.get(i).get(1) - populationBoundaries
							.get(i).get(0) - initSize.get(i))
					+ populationBoundaries.get(i).get(0);
			ArrayList<Double> innerBoundary = new ArrayList<Double>();
			innerBoundary.add(startPos);
			innerBoundary.add(startPos + initSize.get(i));
			boundaries.add(innerBoundary);
		}
	}

	public List<List<Double>> getBoundaries() {
		return boundaries;
	}

	public void setBoundaries(List<List<Double>> boundaries) {
		this.boundaries = boundaries;
	}

	public void setSpecimensInside(Set<ISpecimen> specimensInside) {
		this.specimensInside = specimensInside;
	}

	public Set<ISpecimen> getSpecimensInside() {
		return specimensInside;
	}

	public void setSpecimensBounds(List<IntPair> specimensBounds) {
		this.specimensBounds = specimensBounds;
	}

	public List<IntPair> getSpecimensBounds() {
		return specimensBounds;
	}

	protected void addBetwen(Set<ISpecimen> toAdd, int first, int last,
			List<ISpecimen> from) {
		for (int i = first; i < last; i++) {
			toAdd.add(from.get(i));
		}
	}

	protected void removeBetwen(int first, int last, List<ISpecimen> from) {
		for (int i = first; i < last; i++) {
			specimensInside.remove(from.get(i));
		}
	}

	protected void resizeWindow(List<List<Double>> newCoordinates) {
		Set<ISpecimen> toAdd = new HashSet<ISpecimen>();
		for (int i = 0; i < newCoordinates.size(); i++) {
			int newLower = Clusterizer.find(sortedPopulations.get(i),newCoordinates.get(i).get(0), i);
			int newUpper = Clusterizer.find(sortedPopulations.get(i),newCoordinates.get(i).get(1), i);

			if (newLower > specimensBounds.get(i).a) {
				removeBetwen(specimensBounds.get(i).a, newLower, sortedPopulations.get(i));
			} else if (newLower < specimensBounds.get(i).a) {
				addBetwen(toAdd,newLower, specimensBounds.get(i).a,	sortedPopulations.get(i));
			}

			if (newUpper < specimensBounds.get(i).b) {
				removeBetwen(newUpper, specimensBounds.get(i).b, sortedPopulations.get(i));
			} else if (newUpper > specimensBounds.get(i).b) {
				addBetwen(toAdd, specimensBounds.get(i).b, newUpper, sortedPopulations.get(i));
			}
			specimensBounds.get(i).a = newLower;
			specimensBounds.get(i).b = newUpper;
		}
		
		boundaries.clear();
		for (List<Double> dimBoundaries : newCoordinates) {
			List<Double> newBoundaries = new ArrayList<Double>();
			newBoundaries.addAll(dimBoundaries);
			boundaries.add(newBoundaries);
		}
			
		for (ISpecimen newSpecimen : toAdd) {
			boolean addSpecimen = true;
			for (int i = 0; i < boundaries.size() && addSpecimen; i++) {
				if ((boundaries.get(i).get(0) > newSpecimen.getGenotype().get(i)) || (boundaries.get(i).get(1) < newSpecimen.getGenotype().get(i)))
					addSpecimen = false;
			}
			if (addSpecimen)
				specimensInside.add(newSpecimen);
		}
	}

	
	//funkcja do obliczania nowych koordynatow dla okna
	//zwraca wielkosc kroku
	protected Double calculateMove(List<List<Double>> newCoordinates) {
		newCoordinates.clear();
		Double stepSize = 0.0;
		PopulationCentroidObjectiveFunction centroid = new PopulationCentroidObjectiveFunction();
		IPopulation windowPopulation = new Population();
		windowPopulation.addAll(specimensInside);
		centroid.setPopulationReference(windowPopulation);
		for (int i = 0; i < boundaries.size(); i++) {
			Double windowCenter = (boundaries.get(i).get(1)+boundaries.get(i).get(0))/2;
			Double delta = centroid.getCenter().get(i) - windowCenter;
			stepSize = Math.max(Math.abs(delta),stepSize);
			List<Double> dimCoordinate = new ArrayList<Double>();
			dimCoordinate.add(boundaries.get(i).get(0)+delta);
			dimCoordinate.add(boundaries.get(i).get(1)+delta);
			newCoordinates.add(dimCoordinate);
		}
		return stepSize;
	}

	public void move(Double treshold) {
		List<List<Double>> newCoordinates = new ArrayList<List<Double>>();
		while (calculateMove(newCoordinates) > treshold)
			resizeWindow(newCoordinates);
	}
	
	private void copyCoordinates(List<List<Double>> toFill) {
		toFill.clear();
		for (List<Double> dimCoords : boundaries) {
			List<Double> newDimCoords = new ArrayList<Double>();
			newDimCoords.add(dimCoords.get(0));
			newDimCoords.add(dimCoords.get(1));
			toFill.add(newDimCoords);
		}		
	}
	
	public void enlarge(Double step, Double stop, Double moveThreshold) {
		List<List<Double>> newCoordinates = new ArrayList<List<Double>>();
		List<List<Double>> oldCoordinates = new ArrayList<List<Double>>();
		Double growthFactor = 0.0;
		copyCoordinates(newCoordinates);
		
		for (int dim = 0; dim < boundaries.size(); dim++) {
			//System.out.println("dim: " + dim);
			do {
				copyCoordinates(oldCoordinates);
				int populationSizeBefore = specimensInside.size();
				newCoordinates.get(dim).clear();
				newCoordinates.get(dim).add(boundaries.get(dim).get(0)-step);
				newCoordinates.get(dim).add(boundaries.get(dim).get(1)+step);
				resizeWindow(newCoordinates);
				move(moveThreshold);
				int populationSizeAfter = specimensInside.size();
				growthFactor = (double)(populationSizeAfter - populationSizeBefore)/ (double)populationSizeBefore;
				//System.out.println("GF: " + growthFactor);
			} while(growthFactor >= stop);
			resizeWindow(oldCoordinates);
		}
	}
	
	public void setSortedPopulations(
			List<LinkedList<ISpecimen>> sortedPopulations) {
		this.sortedPopulations = sortedPopulations;
	}

	public List<LinkedList<ISpecimen>> getSortedPopulations() {
		return sortedPopulations;
	}

	public int getOverlapingCount(Window window) {
		Set<ISpecimen> intersection = new HashSet<ISpecimen>();
		intersection.addAll(specimensInside);
		intersection.retainAll(window.specimensInside);
		
		return intersection.size();
	}

}
